﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace IPCByMessages
{
    /// <summary>
    /// Manages packet creation and messages extracting from stream
    /// </summary>
    public class MessageParser
    {
        /// <summary>
        /// The packet is started with this separator
        /// </summary>
        public static readonly byte[] PacketSeparator =
            new byte[] { 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAB };

        /// <summary>
        /// Length of 'Length' field in the packet
        /// </summary>
        public static readonly int LengthFieldLen = 4;

        /// <summary>
        /// Received bytes of packet's message field are written to this array
        /// </summary>
        private byte[] buffer = null;

        /// <summary>
        /// Length of currently parsed packet's message field
        /// </summary>
        private int length = 0;

        /// <summary>
        /// This counter is used to save current position in packet
        /// </summary>
        private int curI = 0;

        /// <summary>
        /// Current packet parsing process state
        /// </summary>
        public ParserState State { get; private set; }

        /// <summary>
        /// Create new instance of class
        /// </summary>
        public MessageParser()
        {
            State = ParserState.Header;
        }

        /// <summary>
        /// Handle new byte read from packet.
        /// </summary>
        /// <param name="newByte">Next byte of packet</param>
        /// <returns>Message content if parsing finished, null otherwise</returns>
        public byte[] HandleNewByte(byte newByte)
        {
            byte[] result = null;

            switch (State)
            {
                case ParserState.Header:
                    if (PacketSeparator[curI] == newByte)
                    {
                        curI++;
                        if (curI == PacketSeparator.Length)
                        {
                            curI = 0;
                            State = ParserState.Length;
                        }
                    }
                    else
                        curI = 0;
                    break;

                case ParserState.Length:
                    length += newByte << (8 * curI);
                    curI++;
                    if (curI == LengthFieldLen)
                    {
                        State = ParserState.Message;
                        curI = 0;
                        buffer = new byte[length];
                    }
                    break;

                case ParserState.Message:
                    if (curI < length)
                    {
                        buffer[curI] = newByte;
                        curI++;
                    }

                    if (curI == length)
                    {
                        result = buffer.Clone() as byte[];
                        curI = 0;
                        buffer = null;
                        length = 0;
                        State = ParserState.Header;
                    }
                    break;
            }

            return result;
        }

        /// <summary>
        /// Wrap data into packet.
        /// Packet format: packetSeparator (7 bytes),
        /// data field length in bytes (4 bytes),
        /// data.
        /// </summary>
        /// <param name="source">User data</param>
        /// <returns>Packet</returns>
        public byte[] MakePacket(byte[] source)
        {
            var length = new byte[LengthFieldLen];
            for (int k = 0; k < LengthFieldLen; k++)
                length[k] = (byte)((source.Length >> (8 * k)) % 256);
              
            var res = new byte[PacketSeparator.Length
                + length.Length
                + source.Length];

            int i = 0;
            int baseI = 0;
            while (i < PacketSeparator.Length)
            {
                res[i] = PacketSeparator[i];
                i++;
            }

            baseI = i;
            while ((i - baseI) < length.Length)
            {
                res[i] = length[i - baseI];
                i++;
            }

            baseI = i;
            while ((i - baseI) < source.Length)
            {
                res[i] = source[i - baseI];
                i++;
            }

            return res;
        }
    }
}
